package com.sinsz.pay.factory.support;

import com.sinsz.common.exception.ApiException;
import com.sinsz.pay.support.Constant;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.springframework.util.ObjectUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;

/**
 * 微信支付xml转换器
 * @author chenjianbo
 */
public final class Parser {

    private SortedMap<Object, Object> map;
    private String xmlString;

    private static final String BEFORE_HEAD = "<xml>";
    private static final String AFTER_HEAD = "</xml>";
    private static final String PREFIX_CDATA = "<![CDATA[";
    private static final String SUFFIX_CDATA = "]]>";

    private Parser() {
    }

    public Parser(SortedMap<Object, Object> map) {
        if (map == null || map.isEmpty()) {
            throw new ApiException(Constant.DEFAULT_EXCEPTION_CODE,"转换内容不能为空");
        }
        this.map = map;
    }

    public Parser(String xmlString) {
        if (StringUtils.isEmpty(xmlString)) {
            throw new ApiException(Constant.DEFAULT_EXCEPTION_CODE,"解析内容不能为空");
        }
        if (!isXML(xmlString)) {
            throw new ApiException(Constant.DEFAULT_EXCEPTION_CODE,"解析内容不是合法的xml格式");
        }
        this.xmlString = xmlString;
    }

    private boolean isXML(String value) {
        try {
            DocumentHelper.parseText(value);
        } catch (DocumentException e) {
            return false;
        }
        return true;
    }

    public SortedMap<Object, Object> getMap() {
        return map;
    }

    public void setMap(SortedMap<Object, Object> map) {
        this.map = map;
    }

    public String getXmlString() {
        return xmlString;
    }

    public void setXmlString(String xmlString) {
        this.xmlString = xmlString;
    }

    private String beforeElement(Object key) {
        if (ObjectUtils.isEmpty(key)) {
            throw new ApiException(Constant.DEFAULT_EXCEPTION_CODE,"解析xml异常.");
        }
        return "<"+key+">";
    }

    private String element(Object value) {
        if (ObjectUtils.isEmpty(value)) {
            throw new ApiException(Constant.DEFAULT_EXCEPTION_CODE,"解析xml异常.");
        }
        return PREFIX_CDATA+value+SUFFIX_CDATA;
    }

    private String afterElement(Object key) {
        if (ObjectUtils.isEmpty(key)) {
            throw new ApiException(Constant.DEFAULT_EXCEPTION_CODE,"解析xml异常.");
        }
        return "</"+key+">";
    }

    private static String objectToXMl(Object source) {
        XStream xStream = new XStream(
                new DomDriver(
                        "UTF-8",
                        new XmlFriendlyNameCoder("-_", "_")
                )
        );
        return xStream.toXML(source);
    }


    private static String objectToXMlAlias(Object source) {
        XStream xStream = new XStream(
                new DomDriver(
                        "UTF-8",
                        new XmlFriendlyNameCoder("-_", "_")
                )
        );
        xStream.alias("xml", source.getClass());
        return xStream.toXML(source);
    }

    private static String objectToXMlWithCDATA(Object source) {
        XStream xStream = initXStream(true);
        xStream.alias("xml", source.getClass());
        return xStream.toXML(source);
    }

    private static InputStream getStringStream(String sInputString) {
        ByteArrayInputStream tInputStringStream = null;
        if (sInputString != null && !"".equals(sInputString.trim())) {
            tInputStringStream = new ByteArrayInputStream(
                    sInputString.getBytes());
        }
        return tInputStringStream;
    }

    private static XStream initXStream(boolean isAddCDATA) {
        XStream xstream = null;
        if (isAddCDATA) {
            xstream = new XStream(new XppDriver() {
                @Override
                public HierarchicalStreamWriter createWriter(Writer out) {
                    return new PrettyPrintWriter(out) {
                        @Override
                        protected void writeText(QuickWriter writer, String text) {
                            if (text.startsWith(PREFIX_CDATA) && text.endsWith(SUFFIX_CDATA)) {
                                writer.write(text);
                            } else {
                                super.writeText(writer, PREFIX_CDATA + text + SUFFIX_CDATA);
                            }
                        }
                    };
                }
            });
        } else {
            xstream = new XStream();
        }
        return xstream;
    }

    public synchronized String mapToXml() {
        if (map == null || map.isEmpty()) {
            throw new ApiException(Constant.DEFAULT_EXCEPTION_CODE,"解析参数为空");
        }
        StringBuilder builder = new StringBuilder();
        builder.append(BEFORE_HEAD);
        map.forEach((key, value) -> builder.append(beforeElement(key)).append(element(value)).append(afterElement(key)));
        builder.append(AFTER_HEAD);
        return builder.toString();
    }

    public synchronized Map<String, Object> xmlToMap()
            throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputStream is = getStringStream(xmlString);
        Document document = builder.parse(is);
        NodeList allNodes = document.getFirstChild().getChildNodes();
        Node node;
        Map<String, Object> map = new HashMap<>(0);
        int i = 0;
        while (i < allNodes.getLength()) {
            node = allNodes.item(i);
            if (node instanceof Element) {
                map.put(node.getNodeName(), node.getTextContent());
            }
            i++;
        }
        return map;
    }

}
